/* Copyright (c) 2025 Beijing Semidrive Technology Corporation
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/** ************************************************************************************************
 * @file     Semag.c                                                                               *
 * @brief    Semaphore Gate Driver                                                                 *
 *                                                                                                 *
 **************************************************************************************************/

/***************************************************************************************************
 *                                      Include header files                                       *
 **************************************************************************************************/
#include "lld_platform.h"
#include <regs_base.h>
#include <Semag.h>

/***************************************************************************************************
 *                                 Private Macro definition                                        *
 **************************************************************************************************/
#define SEMAG_OFF(n)        (0x700UL + (0x10UL * (n)))
#define BM_SEMAG_SGC        (0x1UL << 0UL)
#define BM_SEMAG_IESGLF     (0x1UL << 8UL)
#define BM_SEMAG_IESGSC     (0x1UL << 9UL)
#define BM_SEMAG_SGLIC      (0x1UL << 24UL)

#define SEMAG_SGLS_OFF(n)   (0x704UL + (0x10UL * (n)))
#define BM_SEMAG_SGLS       (0x1UL << 0UL)
#define FM_SEMAG_SGLS       (0x3ffUL)
#define GFV_SEMAG_SGLS(v)   ((v) & FM_SEMAG_SGLS)

#define SGR_OFF             (0xb00UL)
#define BM_SGR_PSGR         (0x1UL << 0UL)
#define BM_SGR_GSGRE        (0x1UL << 20UL)
#define BM_SGR_GSGREL       (0x1UL << 21UL)

#define SGR_GSGR_OFF        (0xb04UL)
#define FM_SGR_GSGR         (0x3ffUL)
#define GFV_SGR_GSGR(v)     ((v) & FM_SGR_GSGR)

#define SGI_OFF             (0xb08UL)

#define SEMAG_POLLs         (10UL)  /** Semaphore Gate unlock waits for the maximum valid loop
                                        duration, unlock is theoretically effective immediately.
                                        The time taken for 10 loops does not exceed 0.3 microseconds. */

#if (defined(CFG_PLATFORM_MCAL))
#define MAILBOX_START_SEC_CODE
#include "Mailbox_MemMap.h"
#endif /** #if (defined(CFG_PLATFORM_MCAL)) */
/***************************************************************************************************
 *                                  Private Function Declarations                                  *
 **************************************************************************************************/
/**
 * @brief  Read the semag gate's register.
 *
 * This function is used to read the semag gate's register.
 *
 * @param [in] offset the address offset of the semag gate's register
 * @return the value of the register
 */
static inline uint32_t sdrv_semag_readl(uint32_t offset)
{
    return readl(MB_REG_BASE + offset);
}

/**
 * @brief  Write the semag gate's register.
 *
 * This function is used to write the semag gate's register.
 *
 * @param [in] offset the address offset of the semag gate's register
 */
static inline void sdrv_semag_writel(uint32_t offset, uint32_t value)
{
    writel(value, MB_REG_BASE + offset);
}

/***************************************************************************************************
 *                                  Global Function Declarations                                   *
 **************************************************************************************************/
/**
 * @brief Tries to lock the SEMAG gate.
 *
 * This function tries to lock the specific SEMAG gate. If the gate has been
 * locked by another processor, this function returns an error code.
 *
 * @param [in] gate_idx  Gate number to lock.
 * @return SEMAG_E_INVALID_PARAM  The gate id is unvalid.
 *         SEMAG_E_FAIL  Semag gate has been locked by another processor.
 *         SEMAG_E_OK  Lock the semag gate successfully.
 */
lld_status_t sdrv_semag_trylock(uint32_t gate_idx)
{
    lld_status_t ret = SEMAG_E_OK;
    volatile uint32_t val;
    uint32_t offset;

    /* If gate id is unvalid. */
    if (gate_idx >= SDRV_SEMAG_NUM) {
        ret = SEMAG_E_INVALID_PARAM;
    } /* else not needed */

    if (SEMAG_E_OK == ret) {
        /* Get the address offset of current gate id. */
        offset = SEMAG_OFF(gate_idx);

        /* Lock the semag gate. */
        val = sdrv_semag_readl(offset);
        val |= BM_SEMAG_SGC;
        sdrv_semag_writel(offset, val);

        /* Check the lock status of the gate. */
        if ((sdrv_semag_readl(offset) & BM_SEMAG_SGC) == BM_SEMAG_SGC) {
            ret = SEMAG_E_OK;
        } else {
            ret = SEMAG_E_FAIL;
        }
    } /* else not needed */

    return ret;
}

/**
 * @brief Locks the SEMAG gate.
 *
 * This function locks the specific SEMAG gate. If the gate has been
 * locked by other processors, this function waits until it is unlocked and then
 * lock it.
 *
 * @param [in] gate_idx  Gate number to lock.
 * @return SEMAG_E_INVALID_PARAM  The gate id is unvalid.
 *         SEMAG_E_OK  Lock the semag gate successfully.
 */
lld_status_t sdrv_semag_lock(uint32_t gate_idx)
{
    lld_status_t ret = SEMAG_E_OK;
    uint32_t val;
    uint32_t sg_offset;
    uint32_t sgls_offset;

    if (gate_idx >= SDRV_SEMAG_NUM) {
        ret = SEMAG_E_INVALID_PARAM;
    } /* else not needed */

    if (SEMAG_E_OK == ret) {
        /* Get the address offset of current gate id. */
        sg_offset = SEMAG_OFF(gate_idx);
        /* Get the gate lock status address offset of current gate id. */
        sgls_offset = SEMAG_SGLS_OFF(gate_idx);

        /* Wait for locking gate successfully. */
        do {
            /* Wait for the semag gate to become unlocked.  */
            while ((sdrv_semag_readl(sgls_offset) & FM_SEMAG_SGLS) > 0U) {
                /* doing nothing */
            }
            /* Lock the semag gate. */
            val = sdrv_semag_readl(sg_offset);
            val |= BM_SEMAG_SGC;
            sdrv_semag_writel(sg_offset, val);
        } while (1U != (sdrv_semag_readl(sg_offset) & BM_SEMAG_SGC));
    } /* else not needed */

    return ret;
}

/**
 * @brief Unlocks the SEMAG gate.
 *
 * This function unlocks the specific SEMAG gate. It only writes unlock value
 * to the SEMAG gate register. However, it does not check whether the SEMAG gate
 * is locked by the current processor or not. As a result, if the SEMAG gate is
 * not locked by the current processor, this function has no effect.
 *
 * @param [in] gate_idx  Gate number to unlock.
 * @return SEMAG_E_OK Unlock the semag gate successfully.
 */
lld_status_t sdrv_semag_unlock(uint32_t gate_idx)
{
    lld_status_t ret = SEMAG_E_OK;
    uint32_t offset;
    uint32_t val;
    uint32_t tms = 0U;

    if (gate_idx >= SDRV_SEMAG_NUM) {
        ret = SEMAG_E_INVALID_PARAM;
    } /* else not needed */

    if (SEMAG_E_OK == ret) {
        /* Get the address offset of current gate id. */
        offset = SEMAG_OFF(gate_idx);

        /* Unlock the semag gate. */
        val = sdrv_semag_readl(offset);
        val &= ~BM_SEMAG_SGC;
        sdrv_semag_writel(offset, val);

        /* Get the gate lock status address offset of current gate id. */
        offset = SEMAG_SGLS_OFF(gate_idx);
        /* check lock status. */
        while (GFV_SEMAG_SGLS(sdrv_semag_readl(offset)) > 0U) {
            if (tms > SEMAG_POLLs) {
                ret = SEMAG_E_FAIL;
                break;
            } /* else not needed */
            tms++;
        }
    } /* else not needed */

    return ret;
}

/**
 * @brief Gets the status of the SEMAG gate.
 *
 * This function checks the lock status of a specific SEMAG gate.
 *
 * @param [in] gate_idx  Gate number to check lock status.
 * @return Return -1 if the gate is unlocked, otherwise return the
 * processor number which has locked the gate.
 */
int32_t sdrv_semag_get_lock_proc(uint32_t gate_idx)
{
    int32_t proc_id = -1;
    uint32_t offset;
    uint32_t val;
    uint32_t i;

    if (gate_idx < SDRV_SEMAG_NUM) {
        /* Get the gate lock status address offset of current gate id. */
        offset = SEMAG_SGLS_OFF(gate_idx);

        /* check lock status. */
        val = sdrv_semag_readl(offset);
        val = GFV_SEMAG_SGLS(val);
        for (i = 0U; i < MBOX_PROC_ID_NUM; i++) {
            if ((val & 0x1U) == 0x1U) {
                proc_id = (int32_t)i;
                break;
            } /* else not needed */
            val >>= 1UL;
        }
    } /* else not needed */

    return proc_id;
}

/**
 * @brief Resets all SEMAG gates to an unlocked status.
 *
 * This function resets all SEMAG gate to an unlocked status,
 * it will only unlock gates which locked by itself, can't reset
 * gates which locked by other processors.
 * @return SEMAG_E_OK Reset all semag gates successfully.
 */
lld_status_t sdrv_semag_reset_all_gate(void)
{
    uint32_t val;

    /* Resets all gates to unlocked state which are locked by current processor.
     */
    val = sdrv_semag_readl(SGR_OFF);
    val |= BM_SGR_PSGR;
    sdrv_semag_writel(SGR_OFF, val);

    return SEMAG_E_OK;
}

/**
 * @brief Enable the gate notification interrupt.
 *
 * Gate notification provides such feature, when interrupt is enabled, then core
 * tried to lock the gate and failed, it could get notification when the gate is
 * idle.
 *
 * @param [in] mask Mask of the gate index, for example: (1<<0U) | (1<<1U)
 * means gate 0 and gate 1.
 * @return SEMAG_E_OK Enable the notify interrupt of the semag gate
 * successfully.
 */
lld_status_t sdrv_semag_enable_gate_notify_interrupt(uint32_t mask)
{
    uint32_t offset;
    uint32_t val;
    uint32_t i;

    for (i = 0U; i < SDRV_SEMAG_NUM; i++) {
        if (((1UL << i) & mask) > 0U) {
            /* Get the address offset of current gate id. */
            offset = SEMAG_OFF(i);
            /* Enable the gate notification interrupt. */
            val = sdrv_semag_readl(offset);
            val |= BM_SEMAG_IESGSC;
            sdrv_semag_writel(offset, val);
        } /* else not needed */
    }
    return SEMAG_E_OK;
}

/**
 * @brief Disable the gate notification interrupt.
 *
 * Gate notification provides such feature, when interrupt is enabled, then core
 * tried to lock the gate and failed, it could get notification when the gate is
 * idle.
 *
 * @param [in] mask Mask of the gate index, for example: (1<<0U) | (1<<1U)
 * means gate 0 and gate 1.
 * @return SEMAG_E_OK Disable the notify interrupt of the semag gate
 * successfully.
 */
lld_status_t sdrv_semag_disable_gate_notify_interrupt(uint32_t mask)
{
    uint32_t offset;
    uint32_t val;
    uint32_t i;

    for (i = 0U; i < SDRV_SEMAG_NUM; i++) {
        if (((1UL << i) & mask) > 0U) {
            /* Get the address offset of current gate id. */
            offset = SEMAG_OFF(i);
            /* Disable the gate notification interrupt. */
            val = sdrv_semag_readl(offset);
            val &= ~BM_SEMAG_IESGSC;
            sdrv_semag_writel(offset, val);
        } /* else not needed */
    }
    return SEMAG_E_OK;
}

/**
 * @brief Get the gate notification flags.
 *
 * Gate notification provides such feature, when interrupt is enabled, then core
 * tried to lock the gate and failed, it could get notification when the gate is
 * idle.
 *
 * @return Mask of the gate index, for example: (1<<0U) | (1<<1U) means
 * gate 0 and gate 1 flags are pending.
 */
uint32_t sdrv_semag_get_notify_status(void)
{
    return sdrv_semag_readl(SGI_OFF);
}

/**
 * @brief Clear the gate notification interrupt status.
 *
 * This function clear SEMAG gate IRQ notifications.
 *
 * @param [in] mask Mask of the gate index, for example: (1<<0U) | (1<<1U)
 * means gate 0 and gate 1.
 * @return SEMAG_E_OK Clear the notify interrupt status of the semag gate
 * successfully.
 */
lld_status_t sdrv_semag_clear_notify_status(uint32_t mask)
{
    sdrv_semag_writel(SGI_OFF, mask);
    return SEMAG_E_OK;
}
#if (defined(CFG_PLATFORM_MCAL))
#define MAILBOX_STOP_SEC_CODE
#include "Mailbox_MemMap.h"
#endif /** #if (defined(CFG_PLATFORM_MCAL)) */
/* End of file */
